#include "IpAddress.h"
#include "LibraryOptions.h"

// <editor-fold desc="Name resolution function macro">
#if USE_GETHOSTBYNAME
        #define  RESOLVE_HOSTNAME_OR_IP_STRING(ipAddressOrHostname)                 \
        {                                                                           \
            unsigned int *ipAddrPtr = NULL;                                         \
            struct hostent *hostEntry = gethostbyname(ipAddressOrHostname.c_str()); \
                                                                                    \
            if(hostEntry != NULL)                                                   \
            {                                                                       \
                ipAddrPtr = (unsigned int *) hostEntry->h_addr_list[0];             \
                assignIpFromNetworkOrderInteger(*ipAddrPtr);                        \
            }                                                                       \
            else                                                                    \
            {                                                                       \
                throw AddressResolutionException(h_errno);                          \
            }                                                                       \
                                                                                    \
            assignIpStringFromNetworkOrderAddress(ipAddrNetworkOrder);              \
        }  
#else
        #define RESOLVE_HOSTNAME_OR_IP_STRING(ipAddressOrHostname)                  \
        {                                                                           \
            int functionResult;                                                     \
            unsigned int ipAddrPtr = NULL;                                          \
            struct sockaddr_in * sockaddrPtr = NULL;                                \
            struct addrinfo * result = NULL;                                        \
            struct addrinfo * resultsList = NULL;                                   \
            struct addrinfo hints = {};                                             \
                                                                                    \
            hints.ai_family = AF_INET;                                              \
            functionResult = getaddrinfo(ipAddressOrHostname.c_str(), NULL,         \
                                         &hints, &resultsList);                     \
                                                                                    \
            if(functionResult == 0 && resultsList != NULL)                          \
            {                                                                       \
                result = resultsList;                                               \
                sockaddrPtr = (struct sockaddr_in * ) result->ai_addr;              \
                ipAddrPtr = sockaddrPtr->sin_addr.s_addr;                           \
                                                                                    \
                assignIpFromNetworkOrderInteger(ipAddrPtr);                         \
            }                                                                       \
            else if(resultsList == NULL)                                            \
            {                                                                       \
                throw AddressResolutionException(HOST_NOT_FOUND);                   \
            }                                                                       \
            else                                                                    \
            {                                                                       \
                throw AddressResolutionException(functionResult);                   \
            }                                                                       \
                                                                                    \
            assignIpStringFromNetworkOrderAddress(ipAddrNetworkOrder);              \
                                                                                    \
            freeaddrinfo(resultsList);                                              \
                                                                                    \
        }                                                                           
#endif
// </editor-fold>

IpAddress::IpAddress()
{
    *this = IpAddress::ANY;
}

IpAddress::IpAddress(unsigned int ipAddress, ByteOrder byteOrder)
{
    switch(byteOrder)
    {
        case NETWORK:
            assignIpFromHostOrderInteger(ipAddress);
            break;
        case HOST:
            assignIpFromNetworkOrderInteger(ipAddress);
            break;
    };
    
    assignIpStringFromNetworkOrderAddress(ipAddrNetworkOrder);
}

IpAddress::IpAddress(const string & ipAddressOrHostname)
{
    RESOLVE_HOSTNAME_OR_IP_STRING(ipAddressOrHostname);
}

void IpAddress::assignIpFromHostOrderInteger(unsigned int ipAddress)
{
    ipAddrHostOrder = ipAddress;
    ipAddrNetworkOrder = htonl(ipAddress);
}

void IpAddress::assignIpFromNetworkOrderInteger(unsigned int ipAddress)
{
    ipAddrNetworkOrder = ipAddress;
    ipAddrHostOrder = ntohl(ipAddress);
}

void IpAddress::assignIpStringFromNetworkOrderAddress(unsigned int ipAddress)
{
    struct in_addr ipAddr;
    char *ipStr = NULL;
    
    ipAddr.s_addr = ipAddress;
    ipStr = inet_ntoa(ipAddr);
    
    ipString = string(ipStr);
}

unsigned int IpAddress::getIpAddressInHostOrder() const
{
    return ipAddrHostOrder;
}

unsigned int IpAddress::getIpAddressInNetworkOrder() const
{
    return ipAddrNetworkOrder;
}

const string & IpAddress::toString() const
{
    return ipString;
}

bool IpAddress::operator==(const IpAddress & ipAddress) const
{
    return (this->ipAddrHostOrder == ipAddress.ipAddrHostOrder);
}
    
bool IpAddress::operator!=(const IpAddress & ipAddress) const
{
    return (this->ipAddrHostOrder != ipAddress.ipAddrHostOrder);
}

// Static ANY member initialization
const IpAddress IpAddress::ANY = IpAddress(IpAddress::IP_ADDRESS_ANY);
